﻿/*
 * Сделано в SharpDevelop.
 * Пользователь: CrMosk
 * Дата: 10.05.2013
 * Время: 13:35
 *  
 */
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Linq;

namespace Region.Utils
{
  /// <summary>
  /// Обеспечивает чтение и изменение файлов в zip архиве
  /// </summary>
  public class Zip
  {
    bool m_Lock;
    CRC32Stream crc = new CRC32Stream();
    List<ZipEntry> ZipEntrys;
    Dictionary<String, ZipEntry> FilesData;
    Dictionary<String, ZipEntry> FilesDir;
    ZipEntry EndDir;
    ZipEntry getEntryOffset(Int32 Offset) { foreach (ZipEntry e in ZipEntrys) if (e.Offset == Offset) return e; return null; }
    UInt16 default_Version=45;
    UInt16 default_VersionMin=20;
    UInt16 default_Flag=6;
    class ZipEntry
    {
      Zip m_zip;
      public enum ETypeEntry { LocalFile = 0x04034b50, CentralDirectory = 0x02014b50, EndCentralDirectory = 0x06054b50 };
      Encoding Encoder;
      ETypeEntry m_TypeEntry;            
      ZipEntry m_LinkData;
      public ZipEntry(Zip zip)
      {
          m_zip = zip;
      }
      Int32 m_Offset;
      public Int32 Offset { get { return m_Offset; } }
      public ZipEntry LinkData { get { return m_LinkData; } }
      bool Edit;

      UInt16 m_R1;
      
      
      UInt16 m_Version;
      UInt16 m_VersionMin;
      UInt16 m_Flag;
      UInt16 m_Method;
      UInt16 m_LastTime;
      UInt16 m_LastDate;
      UInt32 m_CRC32;
      Int32 m_CompressSize;
      Int32 m_Size;
      byte[] m_FileName;
      byte[] m_ExtraField;
      byte[] m_Data;
      byte[] m_Comment;
      UInt16 m_DiskNum;
      UInt16 m_InternalAttr;
      UInt32 m_ExternalAttr;
      UInt16 m_NumRecords;
      UInt16 m_NumRecordsInThisDisk;
      Int32 m_OffsetHeader;

      public ETypeEntry TypeEntry { get { return m_TypeEntry; } }
      public UInt16 VersionMin { get { return m_VersionMin; } set { m_VersionMin = value; Edit = true; } }
      public UInt16 Flag { get { return m_Flag; } set { m_Flag = value; Edit = true; } }
      public UInt16 Method { get { return m_Method; } set { m_Method = value; Edit = true; } }
      public UInt16 LastTime { get { return m_LastTime; } set { m_LastTime = value; Edit = true; } }
      public UInt16 LastDate { get { return m_LastDate; } set { m_LastDate = value; Edit = true; } }
      public UInt32 CRC32 { get { return m_CRC32; } set { m_CRC32 = value; Edit = true; }}
      public Int32 OffsetHeader { get { return m_OffsetHeader; } set { m_OffsetHeader = value; Edit = true; } }            
      public UInt16 NumRecords { get { return m_NumRecords; } set { m_NumRecords = value; Edit = true; } }
      public UInt16 NumRecordsInThisDisk { get { return m_NumRecordsInThisDisk; } set { m_NumRecordsInThisDisk = value; Edit = true; } }
      
      public Int32 CompressSize { get { return m_CompressSize; }  set { m_CompressSize=value; }}
      public Int32 Size { get { return m_Size; } set { m_Size = value; } }
      public String FileName { get { return Encoder.GetString(m_FileName); } set { m_FileName = Encoder.GetBytes(value); Edit = true; } }
      public byte[] ExtraField { get { return m_ExtraField; } set { m_ExtraField = value; Edit = true; } }
      public bool write(Stream stream)
      {
          if (this.m_TypeEntry == ETypeEntry.LocalFile)
          {
              m_Offset = (Int32)stream.Position;
              Int64 Start = stream.Position;
              stream.Write(BitConverter.GetBytes((UInt32)this.m_TypeEntry),0,4);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_VersionMin),0,2);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_Flag),0,2);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_Method),0,2);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_LastTime),0,2);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_LastDate),0,2);
              stream.Write(BitConverter.GetBytes((UInt32)this.m_CRC32),0,4);
              stream.Write(BitConverter.GetBytes((UInt32)this.m_CompressSize),0,4);
              stream.Write(BitConverter.GetBytes((UInt32)this.m_Size),0,4);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_FileName.Length),0,2);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_ExtraField.Length), 0, 2);
              Int64 End = stream.Position;
              Int64 Len = End - Start;
              stream.Write(this.m_FileName, 0, this.m_FileName.Length);
              stream.Write(this.m_ExtraField, 0, this.m_ExtraField.Length);
              stream.Write(this.m_Data, 0, this.m_Data.Length);                                        
              return true;
          }
          if (this.m_TypeEntry == ETypeEntry.CentralDirectory)
          {
              m_Offset = (Int32)stream.Position;                    
              stream.Write(BitConverter.GetBytes((UInt32)this.m_TypeEntry), 0, 4);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_Version), 0, 2);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_VersionMin), 0, 2);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_Flag), 0, 2);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_Method), 0, 2);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_LastTime), 0, 2);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_LastDate), 0, 2);
              stream.Write(BitConverter.GetBytes((UInt32)this.m_CRC32), 0, 4);
              stream.Write(BitConverter.GetBytes((UInt32)this.m_CompressSize), 0, 4);
              stream.Write(BitConverter.GetBytes((UInt32)this.m_Size), 0, 4);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_FileName.Length), 0, 2);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_ExtraField.Length), 0, 2);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_Comment.Length), 0, 2);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_DiskNum), 0, 2);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_InternalAttr), 0, 2);
              stream.Write(BitConverter.GetBytes((UInt32)this.m_ExternalAttr), 0, 4);
              stream.Write(BitConverter.GetBytes((UInt32)this.m_OffsetHeader), 0, 4);                    
              stream.Write(this.m_FileName, 0, this.m_FileName.Length);
              stream.Write(this.m_ExtraField, 0, this.m_ExtraField.Length);
              stream.Write(this.m_Comment, 0, this.m_Comment.Length);
              return true;                    
          }
          if (this.m_TypeEntry == ETypeEntry.EndCentralDirectory)
          {                    
              stream.Write(BitConverter.GetBytes((UInt32)this.m_TypeEntry), 0, 4);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_DiskNum), 0, 2);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_R1), 0, 2);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_NumRecordsInThisDisk), 0, 2);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_NumRecords), 0, 2);
              stream.Write(BitConverter.GetBytes((UInt32)this.m_Size), 0, 4);
              stream.Write(BitConverter.GetBytes((UInt32)this.m_OffsetHeader), 0, 4);
              stream.Write(BitConverter.GetBytes((UInt16)this.m_Comment.Length), 0, 2);
              stream.Write(this.m_Comment, 0, this.m_Comment.Length);
              return true;
          }
          return false;
      }
      public bool newLocalFile(String FileName)
      {                
        Encoder = ((m_zip.default_Flag & 0x0800) == 0x0800) ? Encoding.UTF8 : Encoding.GetEncoding(866);
        Byte[] btFileName = Encoder.GetBytes(FileName);                
        this.m_TypeEntry = ETypeEntry.LocalFile;
        this.m_VersionMin = m_zip.default_VersionMin;
        this.m_Flag = m_zip.default_Flag;
        this.m_Method = 8;
        this.m_LastTime = 0;
        this.m_LastDate = 33;
        this.m_CRC32 = 0;
        this.m_CompressSize = 0;
        this.m_Size =0;
;
        this.m_FileName = btFileName;
        this.m_ExtraField = new Byte[0];
        this.m_Data = new Byte[0];        
        this.Edit = true;
        m_zip.FilesData[FileName] = this;        
        return true;
      }
      public bool newCentralDirectory(String FileName)
      {        
        m_zip.EndDir.m_NumRecords+=1;        
        Encoder = ((m_zip.default_Flag & 0x0800) == 0x0800) ? Encoding.UTF8 : Encoding.GetEncoding(866);
        Byte[] btFileName = Encoder.GetBytes(FileName);                

        this.m_TypeEntry = ETypeEntry.CentralDirectory;
        this.m_Version = m_zip.default_Version;
        this.m_VersionMin = m_zip.default_VersionMin;
        this.m_Flag = m_zip.default_Flag;
        this.m_Method = 8;
        this.m_LastTime = 0;
        this.m_LastDate = 33;
        this.m_CRC32 = 0;
        this.m_CompressSize = 0;
        this.m_Size = 0;
        this.m_DiskNum = 0;
        this.m_InternalAttr = 0;
        this.m_ExternalAttr = 0;
        this.m_OffsetHeader = 0;
        this.m_FileName = btFileName;                  
        this.m_ExtraField = new Byte[0];
        this.m_Comment = new Byte[0];
        this.m_Data = null;
        this.Edit = true;
        m_zip.FilesDir[this.FileName] = this;
        this.m_LinkData = m_zip.FilesData[FileName];
        
        return true;
      }
      public bool newEndCentralDirectory()
      {
        this.m_TypeEntry = ETypeEntry.EndCentralDirectory;
        this.m_Comment = new byte[0];
        return true;
      }
      public bool read(Stream stream)
      {
          Byte[] Buff = new Byte[64];
          m_Offset = (Int32)stream.Position;
          stream.Read(Buff, 0, 4);
          UInt32 Sign = BitConverter.ToUInt32(Buff,0);
          
          if (Sign == (UInt32)ETypeEntry.LocalFile)
          {
              stream.Read(Buff, 4, 30 - 4);// 30 header size
              this.m_TypeEntry = ETypeEntry.LocalFile;
              this.m_VersionMin = BitConverter.ToUInt16(Buff,  4);
              this.m_Flag = BitConverter.ToUInt16(Buff,  6);
              this.m_Method = BitConverter.ToUInt16(Buff,  8);
              this.m_LastTime = BitConverter.ToUInt16(Buff,  10);
              this.m_LastDate = BitConverter.ToUInt16(Buff,  12);
              this.m_CRC32 = BitConverter.ToUInt32(Buff,  14);
              this.m_CompressSize = BitConverter.ToInt32(Buff,  18);
              this.m_Size = BitConverter.ToInt32(Buff,  22);
              UInt16 FileNameSize = BitConverter.ToUInt16(Buff,  26);
              UInt16 ExtraFieldSize = BitConverter.ToUInt16(Buff, 28);
              this.m_FileName = new Byte[FileNameSize];
              this.m_ExtraField = new Byte[ExtraFieldSize];
              this.m_Data = new Byte[this.m_CompressSize];
              stream.Read(this.m_FileName, 0, FileNameSize);
              stream.Read(this.m_ExtraField, 0, ExtraFieldSize);
              stream.Read(this.m_Data, 0, (Int32)this.m_CompressSize);
              Encoder = ((this.Flag & 0x0800) == 0x0800) ? Encoding.UTF8 : Encoding.GetEncoding(866);
              this.Edit = false;
              m_zip.FilesData[this.FileName] = this;
              System.Console.WriteLine("Read LocalFile:[" + m_Offset.ToString() + "] " + Encoder.GetString(this.m_FileName));
              m_zip.default_VersionMin = this.m_VersionMin;
              m_zip.default_Flag = this.m_Flag;
              return true;
          }
          else if (Sign == (UInt32)ETypeEntry.CentralDirectory)
          {
              stream.Read(Buff, 4, 46 - 4);// 46 header size
              this.m_TypeEntry = ETypeEntry.CentralDirectory;
              this.m_Version = BitConverter.ToUInt16(Buff,  4);
              this.m_VersionMin = BitConverter.ToUInt16(Buff,   6);
              this.m_Flag = BitConverter.ToUInt16(Buff,  8);
              this.m_Method = BitConverter.ToUInt16(Buff,  10);
              this.m_LastTime = BitConverter.ToUInt16(Buff,  12);
              this.m_LastDate = BitConverter.ToUInt16(Buff,  14);
              this.m_CRC32 = BitConverter.ToUInt32(Buff,  16);
              this.m_CompressSize = BitConverter.ToInt32(Buff,  20);
              this.m_Size = BitConverter.ToInt32(Buff,  24);
              UInt16 FileNameSize = BitConverter.ToUInt16(Buff,  28);
              UInt16 ExtraFieldSize = BitConverter.ToUInt16(Buff,  30);
              UInt16 CommentSize = BitConverter.ToUInt16(Buff,  32);
              this.m_DiskNum = BitConverter.ToUInt16(Buff,  34);
              this.m_InternalAttr = BitConverter.ToUInt16(Buff,  36);
              this.m_ExternalAttr = BitConverter.ToUInt32(Buff,  38);
              this.m_OffsetHeader = BitConverter.ToInt32(Buff,  42);
              this.m_FileName = new Byte[FileNameSize];                    
              this.m_ExtraField = new Byte[ExtraFieldSize];
              this.m_Comment = new Byte[CommentSize];
              this.m_Data = null;
              stream.Read(this.m_FileName, 0, FileNameSize);
              stream.Read(this.m_ExtraField, 0, ExtraFieldSize);
              stream.Read(this.m_Comment, 0, (Int32)CommentSize);
              Encoder = ((this.Flag & 0x0800) == 0x0800) ? Encoding.UTF8 : Encoding.GetEncoding(866);
              this.Edit = false;
              m_zip.FilesDir[this.FileName] = this;
              this.m_LinkData = m_zip.getEntryOffset(this.m_OffsetHeader);
              System.Console.WriteLine("Read CentralDirectory:[" + m_Offset.ToString() + "] " + Encoder.GetString(this.m_FileName));
              m_zip.default_Version = this.m_Version;
              return true;
          }
          else if (Sign == (UInt32)ETypeEntry.EndCentralDirectory)
          {
              stream.Read(Buff, 4, 22 - 4);// 46 header size
              this.m_TypeEntry = ETypeEntry.EndCentralDirectory;
              this.m_DiskNum = BitConverter.ToUInt16(Buff,  4);
              this.m_R1 = BitConverter.ToUInt16(Buff, 6);
              this.m_NumRecordsInThisDisk = BitConverter.ToUInt16(Buff, 8);                    
              //6
              //8
              this.m_NumRecords = BitConverter.ToUInt16(Buff,  10);
              this.m_Size = BitConverter.ToInt32(Buff, 12);
              this.m_OffsetHeader = BitConverter.ToInt32(Buff,  16);
              UInt16 CommentSize = BitConverter.ToUInt16(Buff,  20);
              this.m_FileName = null;
              this.m_ExtraField = null;
              this.m_Comment = new Byte[CommentSize];
              this.m_Data = null;
              stream.Read(this.m_Comment, 0, (Int32)CommentSize);
              Encoder = Encoding.GetEncoding(866);
              this.Edit = false;
              m_zip.EndDir = this;
              System.Console.WriteLine("Read EndCentralDirectory:[" + m_Offset.ToString() + "] ");
              return true;
          }

          return false;
      }
      public byte[] readDataAll() 
      {
          if (m_TypeEntry != ETypeEntry.LocalFile) return new byte[0];
          byte[] Data=new byte[m_Size];
          MEMStream mem = new MEMStream(this.m_Data);
          //UInt32 C1 = m_zip.CRC32(Buff, (Int32)(Offset + m_SizeEntry - m_CompressSize), (Int32)m_CompressSize);
          System.IO.Compression.DeflateStream s = new System.IO.Compression.DeflateStream(mem,System.IO.Compression.CompressionMode.Decompress,false);
          s.Read(Data, 0, (Int32)m_Size);
          m_zip.crc.Restart();
          m_zip.crc.Write(Data, 0, (Int32)m_Size);
          UInt32 CRC = m_zip.crc.CRC;                

          return Data;
      }
      public bool writeDataAll(Byte[] Buff,Int32 Offset,Int32 Count)
      {
          m_zip.crc.Restart();
          m_zip.crc.Write(Buff, Offset, Count);
          this.m_CRC32 = m_zip.crc.CRC;
          this.m_Size = Count;

          //byte[] Data = new byte[Count+32*1024];
          MEMStream mem = new MEMStream();
          //UInt32 C1 = m_zip.CRC32(Buff, (Int32)(Offset + m_SizeEntry - m_CompressSize), (Int32)m_CompressSize);
          System.IO.Compression.DeflateStream s = new System.IO.Compression.DeflateStream(mem, System.IO.Compression.CompressionMode.Compress, true);                
          
          s.Write(Buff, Offset, Count);                
          s.Close();
          this.m_CompressSize = (Int32)mem.Length;
          this.m_Data = new byte[this.m_CompressSize];
          mem.Position = 0;
          mem.Read(this.m_Data, 0, this.m_CompressSize);
          mem.Close();
          return true;
      }
    }
    public Zip()
    {
        m_Lock = false;
        this.clear();
    }
    bool clear()
    {
        if (m_Lock) return false;            
        ZipEntrys = new List<ZipEntry>();
        FilesData = new Dictionary<String, ZipEntry>();
        FilesDir = new Dictionary<String, ZipEntry>();
        EndDir = null;
        return true;
    }
    public static Zip EmptyZip()
    {
      Zip z = new Zip();
      var End = new Zip.ZipEntry(z);
      End.newEndCentralDirectory();
      z.EndDir = End;
      return z;
    }
    //Считать ZIP архив из файла в память
    public bool readZIP(String FileName)
    {
      using (Stream stream = new FileStream(FileName, FileMode.Open, FileAccess.Read))
      {
        return readHeader(stream);
      }
    }
    public bool readZIP(Stream stream){return readHeader(stream);}
    
    public bool writeZIP(String FileName)
    {
      using (Stream stream = new FileStream(FileName, FileMode.Create, FileAccess.Write))
      {
        return writeHeader(stream);
      }
    }        
    bool readHeader(Stream stream)
    {
        if (!clear()) return false;                     
        while(true)
        {
            ZipEntry f = new ZipEntry(this);                
            if (!f.read(stream)) break;                
            ZipEntrys.Add(f);
            if (f.TypeEntry == ZipEntry.ETypeEntry.EndCentralDirectory) 
                return true;                
        }            
        return false;
    }
    bool writeHeader(Stream stream)
    {            
        String[] FileList = FilesDir.Keys.ToArray();;
        for(Int32 i=0;i<FileList.Length;i++)
        {
            FilesData[FileList[i]].write(stream);                
        }
        EndDir.OffsetHeader = (Int32)stream.Position;
        EndDir.NumRecords = 0;
        Int64 StartCD = stream.Position;
        for(Int32 i=0;i<FileList.Length;i++)
        {                
            ZipEntry E = FilesDir[FileList[i]];
            E.OffsetHeader = E.LinkData.Offset;
            E.CRC32 = E.LinkData.CRC32;
            E.Method = E.LinkData.Method;
            E.Size = E.LinkData.Size;
            E.CompressSize = E.LinkData.CompressSize;
            E.write(stream);
            EndDir.NumRecords++;
        }
        Int64 EndCD = stream.Position;
        EndDir.NumRecordsInThisDisk = EndDir.NumRecords;
        EndDir.Size = (Int32)(EndCD-StartCD);
        EndDir.write(stream);
        return true;
    }
    public bool isFile(String FileName) { return FilesDir.ContainsKey(FileName); }
    public String[] filesList() { return FilesDir.Keys.ToArray(); }
    public byte[] readAll(String FileName)
    {
        if (!FilesDir.ContainsKey(FileName)) return new byte[0];
        if (FilesDir[FileName].LinkData == null) return new byte[0];
        return FilesDir[FileName].LinkData.readDataAll();
    }
    bool createFile(string FileName)
    {
      ZipEntry Data = new ZipEntry(this);                
      Data.newLocalFile(FileName);      
      ZipEntrys.Add(Data);
      ZipEntry Dir = new ZipEntry(this);                
      Dir.newCentralDirectory(FileName);      
      ZipEntrys.Add(Dir);
      return true;
    }
    public bool writeAll(String FileName,byte[] Data){return writeAll(FileName,Data,0,Data.Length);}
    public bool writeAll(String FileName,byte[] Data,Int32 Offset,Int32 Count)
    {
      if (!FilesDir.ContainsKey(FileName)) 
        if (!createFile(FileName)) return false;
      if (FilesDir[FileName].LinkData == null) return false;
      return FilesDir[FileName].LinkData.writeDataAll(Data,Offset,Count);
    }
  }
}
